/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.nightknight.satobee.core;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author nightknight
 */
public class FileServer extends Thread {

    private ServerSocket server;
    private Map<String, Pair> files = new HashMap<String, Pair>();
    
    private FileSender fileSender = new FileSender();

    private FileServer() {
        try {
            server = new ServerSocket(8200);
        } catch (IOException ex) {
            Logger.getLogger(FileServer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    @Override
    public void run() {
        fileSender.start();
        
        Conversation conv = null;
        String str = null;
        
        byte[] buffer = new byte[4096];
        int length = 0;
        int nch = 0;
        
        while (true) {
            try {
                Socket incoming = server.accept();
                DataInputStream in = new DataInputStream(incoming.getInputStream());

                length = in.readInt();
                in.read(buffer, 0, length);
                str = new String(buffer, 0, length);
                
                Set<String> strs = files.keySet();
                Iterator<String> it = strs.iterator();
                File f = files.get(str).getFile();
                conv = files.get(str).getConv();
                
                if (f == null) {
                    f = new File("tmp/" + str);
                    int i = 0;
                    while (f.exists()) {
                        ++i;
                        f = new File("tmp/" + str + i);
                    }
                }

                conv.startFileReceive(str);
                OutputStream out = new FileOutputStream(f);

                while ((nch = in.read(buffer, 0, buffer.length)) != -1) {
                    out.write(buffer, 0, nch);
                    conv.fileReceiving(nch, str);
                }

                incoming.close();
                in.close();
                out.close();
            } catch (Exception e) {
                Logger.getLogger(FileServer.class.getName()).log(Level.SEVERE, null, e);
            } finally {
                if (conv != null && str != null) {
                    conv.fileReceived(str);
                }
            }
        }
    }

    // Accept to receive the file the contact want to send to you.
    public void acceptFileRec(String fname, File file, Conversation conv) {
        files.put(fname, new Pair(file, conv));
    }
    
    public void sendFile(File file, Conversation conv) {
        fileSender.enqueue(file, conv);
    }
    
    public void fileAccepted(File file, Conversation conv) {
        fileSender.acceptFile(file, conv);
    }
    
    public void fileRefused(File file, Conversation conv) {
        fileSender.refuseFile(file, conv);
    }

    class FileSender extends Thread {

        
        private List<Pair> waitings = new ArrayList<Pair>();
        private List<Pair> readys = new ArrayList<Pair>();
        private Pair p;
        private final Object sync = new Object();

        public void enqueue(File file, Conversation conv) {
            waitings.add(new Pair(file, conv));
        }
        
        public void acceptFile(File file, Conversation conv) {
            synchronized (sync) {
                Pair pair = new Pair(file, conv);
                int i = 0;
                for(Pair pa : waitings) {
                    if (pair.equals(pa)) {
                        break;
                    }
                    ++i;
                }
                
                readys.add(waitings.remove(i));
                sync.notifyAll();
            }
        }
        
        public void refuseFile(File file, Conversation conv) {
            synchronized (sync) {
                waitings.remove(new Pair(file, conv));
            }
        }

        @Override
        public void run() {
            int nch;
            byte[] buffer = new byte[4096];
            while (true) {
                synchronized (sync) {
                    if (readys.isEmpty()) {
                        try {
                            sync.wait();
                        } catch (InterruptedException ex) {
                            Logger.getLogger(FileServer.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }

                    p = readys.remove(0);
                }
                //start sending
                Conversation conv = p.getConv();
                File f = p.getFile();
                conv.startFileSend();
                try {
                    Socket s = new Socket(conv.getContact().getAddresss(), 8200);
                    InputStream in = new FileInputStream(f);
                    DataOutputStream out = new DataOutputStream(s.getOutputStream());
//                    Writer out = new PrintWriter(s.getOutputStream());

                    out.writeInt(f.getAbsolutePath().getBytes().length);
                    out.write(f.getAbsolutePath().getBytes());

                    while ((nch = in.read(buffer, 0, buffer.length)) != -1) {
                        out.write(buffer, 0, nch);
                        conv.fileSending(nch);
                    }

                    s.close();
                    out.close();
                    in.close();
                } catch (IOException ex) {
                    Logger.getLogger(FileServer.class.getName()).log(Level.SEVERE, null, ex);
                } finally {
                    conv.fileSent();
                }
            }
        }
    }
    
    class Pair {

        private File file;
        private Conversation conv;

        public File getFile() {
            return file;
        }

        public Conversation getConv() {
            return conv;
        }

        public Pair(File f, Conversation c) {
            this.file = f;
            this.conv = c;
        }

        public boolean equals(Pair pair) {
            return (file.equals(pair.getFile()) && conv.equals(pair.getConv()));
        }
    }
    
    private static FileServer fileServer = new FileServer();
    
    public static FileServer getFileServer() {
        return fileServer;
    }
}
